using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using ShevaEngine.Core.Interfaces;

namespace ShevaEngine.Core.Modules.Animations
{
    /// <summary>
    /// Animates and draws a model that was processed with AnimatedModelProcessor
    /// </summary>
    public class ModelAnimator
    {
        /// <summary>Skeletal structure containg transforms.</summary>
        private BonePoseCollection _BonePoses;
        /// <summary>Animations.</summary>
        private AnimationInfoCollection _Animations;
        /// <summary>List of attached objects.</summary>
        private Dictionary<BonePose, IAttachAble> _AttachedObjects;
        /// <summary>Store the number of meshes in the model.</summary>
        private readonly int _NumMeshes;
        /// <summary>Used to avoid reallocation.</summary>
        private static Matrix _SkinTransform;
        /// <summary>Buffer for storing absolute bone transforms.</summary>
        private Matrix[] _Pose;
        /// <summary>Array used for the matrix palette.</summary>
        private Matrix[][] _MatrixPalette;
        /// <summary>Inverse reference pose transforms.</summary>
        private SkinInfoCollection[] _SkinInfo;


        /// <summary>
        /// Gets the animations that were loaded in from the content pipeline
        /// for this model.
        /// </summary>
        public AnimationInfoCollection Animations
        {
            get { return _Animations; }
        }

        /// <summary>
        /// Creates a new instance of ModelAnimator.
        /// </summary>
        /// <param name="game">The game to which this component will belong.</param>
        /// <param name="model">The model to be animated.</param>
        public ModelAnimator(Model model)
        {

            this._AttachedObjects = new Dictionary<BonePose, IAttachAble>();
            this._Animations = AnimationInfoCollection.FromModel(model);
            this._BonePoses = BonePoseCollection.FromModelBoneCollection(model.Bones);

            this._NumMeshes = model.Meshes.Count;

            this._Pose = new Matrix[model.Bones.Count];

            model.CopyAbsoluteBoneTransformsTo(_Pose);

            // Get all the skinning info for the model
            Dictionary<string, object> modelTagInfo = (Dictionary<string, object>)model.Tag;
            if (modelTagInfo == null)
                throw new Exception("Model Processor must subclass AnimatedModelProcessor.");

            this._SkinInfo = (SkinInfoCollection[])modelTagInfo["SkinInfo"];
            if (this._SkinInfo == null)
                throw new Exception("Model processor must pass skinning info through the tag.");

            this._MatrixPalette = new Matrix[model.Meshes.Count][];
            for (int i = 0; i < _SkinInfo.Length; i++)
            {
                if (Util.IsSkinned(model.Meshes[i]))
                    this._MatrixPalette[i] = new Matrix[_SkinInfo[i].Count];
                else
                    this._MatrixPalette[i] = null;
            }

            // Test to see if model has too many bones
            //for (int i = 0; i < model.Meshes.Count; i++ )
            //{
            //    if (palette[i] != null && matrixPaletteParams[i] != null)
            //    {
            //        Matrix[] meshPalette = palette[i];
            //        try
            //        {
            //            matrixPaletteParams[i].SetValue(meshPalette);
            //        }
            //        catch
            //        {
            //            throw new Exception("Model has too many skinned bones for the matrix palette.");
            //        }
            //    }
            //}
        }

        /// <summary>
        /// Returns skinning information for a mesh.
        /// </summary>
        /// <param name="index">The index of the mesh.</param>
        /// <returns>Skinning information for the mesh.</returns>
        public SkinInfoCollection GetMeshSkinInfo(int index)
        {
            return _SkinInfo[index];
        }

        /// <summary>
        /// Updates the animator by finding the current absolute transforms.
        /// </summary>
        /// <param name="gameTime">The GameTime.</param>
        public void Update(GameTime gameTime)
        {/*
            this._BonePoses.CopyAbsoluteTransformsTo(_Pose);

            for (int i = 0; i < _SkinInfo.Length; i++)
            {
                if (_MatrixPalette[i] == null)
                    continue;
                SkinInfoCollection infoCollection = _SkinInfo[i];
                foreach (SkinInfo info in infoCollection)
                {
                    _SkinTransform = info.InverseBindPoseTransform;
                    Matrix.Multiply(ref _SkinTransform, ref _Pose[info.BoneIndex],
                       out _MatrixPalette[i][info.PaletteIndex]);
                }
            }

            foreach (KeyValuePair<BonePose, IAttachAble> item in this._AttachedObjects)
            {
                item.Value.TotalTransform = item.Value.LocalTransform *
                    Matrix.Invert(this._Pose[this.Model.Meshes[0].ParentBone.Index]) *
                    this._Pose[item.Key.BoneIndex];
            }*/
        }


        /// <summary>
        /// Copies the current absolute transforms to the specified array.
        /// </summary>
        /// <param name="transforms">The array to which the transforms will be copied.</param>
        public void CopyAbsoluteTransformsTo(Matrix[] transforms)
        {
            _Pose.CopyTo(transforms, 0);
        }

        /// <summary>
        /// Gets the current absolute transform for the given bone index.
        /// </summary>
        /// <param name="boneIndex"></param>
        /// <returns>The current absolute transform for the bone index.</returns>
        public Matrix GetAbsoluteTransform(int boneIndex)
        {
            return _Pose[boneIndex];
        }


        /// <summary>
        /// Gets the BonePoses associated with this ModelAnimator.
        /// </summary>
        public BonePoseCollection BonePoses
        {
            get { return _BonePoses; }
        }
        /*
        /// <summary>
        /// Draws the current frame
        /// </summary>
        /// <param name="gameTime">The game time</param>
        public void Draw(RendererState rState)
        {
            GraphicsEngine.Instance.GraphicsDevice.SetVertexBuffer(this._Model.Meshes[0].MeshParts[0].VertexBuffer);

            for (int i = 0; i < _NumMeshes; i++)
            {
                RasterizerState state = new RasterizerState();
                state.CullMode = CullMode.None;

                GraphicsEngine.Instance.GraphicsDevice.RasterizerState = state;
                //GraphicsEngine.Instance.GraphicsDevice.RenderState.CullMode = CullMode.None;
                rState.Material.Parameters["MatrixPalette"].SetValue(this._MatrixPalette[i]);

                rState.Material.Bind(rState);
                rState.Material.Apply();

                ModelMesh mesh = this._Model.Meshes[i];

                int numParts = mesh.MeshParts.Count;

                GraphicsEngine.Instance.GraphicsDevice.Indices = mesh.MeshParts[0].IndexBuffer;


                for (int j = 0; j < numParts; j++)
                {
                    ModelMeshPart currentPart = mesh.MeshParts[j];
                    if (currentPart.NumVertices == 0 || currentPart.PrimitiveCount == 0)
                        continue;

                    rState.Material.Bind(rState);

                    GraphicsEngine.Instance.GraphicsDevice.DrawIndexedPrimitives(
                        PrimitiveType.TriangleList, currentPart.VertexOffset,
                        0, currentPart.NumVertices, currentPart.StartIndex, currentPart.PrimitiveCount);
                }
            }
        }*/

        /// <summary>
        /// Metoda pripoji objekt.
        /// </summary>
        /// <param name="attachable"></param>
        public void AddAttachable(IAttachAble attachable, string name)
        {
            this._AttachedObjects.Add(this.BonePoses[name], attachable);
        }
    }
}
